#ifndef __BASIC_DK_MEMORY_H__
#define __BASIC_DK_MEMORY_H__

#include <memory>
#include "basic/platform.h"
#include "basic/types.h"
#include "basic/log.h"

namespace basic {
class DKMemoryAllocator;

class M_API_FUNC DKMemoryBlock final {
  M_DELETE_COPY_ASSIGN(DKMemoryBlock)
public:
  DKMemoryBlock() = delete;
  DKMemoryBlock(unsigned long long); /// size in bytes;
  ~DKMemoryBlock();
  inline unsigned long long GetPreservingSize() const {
    return m_nCapability;
  }

private:
  friend class DKMemoryAllocator;
  unsigned long long m_nCapability{1024u}; // 1KiB
  unsigned char* m_pBuffer{nullptr};
};

class M_API_FUNC DKMemoryAllocator {
  M_DELETE_COPY_ASSIGN(DKMemoryAllocator)
public:
  DKMemoryAllocator() = default;
  ~DKMemoryAllocator() = default;
  
  /**
   * takes ownership of pb
   */
  void SetMemoryBlock(DKMemoryBlock* pb);
  void SetMemoryBlock(std::shared_ptr<DKMemoryBlock> b, const DKSRange<unsigned long long>&& r);

  /// allocate `n` T instances;
  template <typename T>
  T* Allocate(uint32_t n) {
    if (n <= 0) {
      M_LOG_WARN("allocating count MUST bigger than 0, argument is:", n);
      return nullptr;
    }

    const unsigned long long _l{sizeof(T) * n};
    const unsigned long long _e{m_pivot + _l};
    if (_e > m_range.length) {
      M_LOG_FATAL("allocating space in block-range was failed! range begin:", m_range.begin,
                  ", length:", m_range.length, "bytes, allocating begin:", m_pivot, ", length:", _l, "bytes")
      return nullptr;
    }

    T* r = new (m_ptrBlock->m_pBuffer + m_range.begin + m_pivot) T[n];
    m_pivot = _e;
    return r;
  }
  void FreeAll() {
    m_pivot = 0u;
  }

private:
  std::shared_ptr<DKMemoryBlock> m_ptrBlock;
  DKSRange<unsigned long long> m_range;
  unsigned long long m_pivot{0u};
};

} // namespace basic

#endif
